#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <time.h>
#include <sys/stat.h>
#include "config.h"
#include "log.h"
#include "user_db.h"
#include "broadcast.h"
#include "client_handler.h"

// 全局变量
Client clients[MAX_CLIENTS];// 客户端数组
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 互斥锁
ServerConfig server_config;// 服务器配置

/**
 *  聊天室服务器的主函数，负责初始化服务器、监听客户端连接并处理。
 * 
 * 该函数完成服务器的初始化工作，包括读取配置、初始化用户数据库、
 * 创建套接字、绑定地址、监听连接等操作。在循环中持续接受客户端连接，
 * 为每个客户端创建独立线程进行处理。
 * 
 * return int 程序退出状态码，0 表示正常退出。
 */
int main() {
    // 服务器套接字和客户端套接字文件描述符
    int server_socket, client_socket;
    // 服务器地址结构体和客户端地址结构体，存储服务器和客户端的网络地址信息
    struct sockaddr_in server_addr, client_addr;
    // 客户端地址结构体长度，用于存储客户端地址信息的长度
    socklen_t client_addr_len = sizeof(client_addr);
    // 线程 ID，用于标识新创建的线程
    pthread_t thread_id;

    // 初始化服务端配置
    //需求：测试更改。字符串的定义、初始化、使用/引用复习
    read_server_config(&server_config);   // 调用 read_server_config 函数读取服务器配置文件
    // 打印服务器的配置信息，包括端口号、日志目录和日志文件名
    printf("服务器配置: Port=%d, LogDir=%s, LogFile=%s, MaxClients=%d\n",
           server_config.port, server_config.log_dir, server_config.log_file, server_config.max_clients);

    // 初始化用户数据库
    init_user_db(); // 调用 init_user_db 函数初始化用户数据库

    // 初始化客户端数组
    for (int i = 0; i < MAX_CLIENTS; i++) { // 遍历客户端数组
        clients[i].socket = -1; //socket文件描述符为-1，说明该客户端未连接
        clients[i].in_private_mode = 0; // 私聊模式标记，0表示非私聊模式（群聊）
        clients[i].is_authenticated = 0; // 用户认证状态标记，0表示未认证
    }

    // 创建套接字：使用 socket 函数创建一个 TCP 套接字
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    // 检查套接字是否创建成功，若失败则打印错误信息并退出程序
    if (server_socket == -1) {
        perror("套接字创建失败");
        exit(EXIT_FAILURE);
    }

    // 设置SO_REUSEADDR,让服务器在端口未完全释放的情况下，依然可以重新绑定该端口
    int opt = 1; // 将 opt 设为 1 表示启用 SO_REUSEADDR 选项 
    //若返回非零值（即 -1，表示设置失败），则执行 if 语句块内的代码，打印错误信息、关闭套接字并退出程序。
    //SOL_SOCKET：指定选项的级别，这里是套接字级别。
    //SO_REUSEADDR：指定要设置的选项，这里是允许重用本地地址。
    //&opt：指向选项值的指针，这里传递 opt 变量的地址，以指示是否启用 SO_REUSEADDR 选项。
    //sizeof(opt)：选项值的长度，以字节为单位。
    if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {   // 检查是否启用成功
        perror("设置SO_REUSEADDR失败");
        // 关闭已创建的套接字
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    // 绑定地址
    // 将服务器地址结构体清零
    memset(&server_addr, 0, sizeof(server_addr));
    // 设置地址族为 IPv4
    server_addr.sin_family = AF_INET;
    // 服务器监听本地回环地址 127.0.0.1
    //inet_addr：将点分十进制的 IP 地址转换为网络字节序的 32 位整数。
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    // 将配置文件中的端口号转换为网络字节序
    server_addr.sin_port = htons(server_config.port);

    // 使用 bind 函数将套接字绑定到指定地址和端口
    if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {  // 检查绑定是否成功
        perror("绑定失败");
        // 关闭已创建的套接字
        close(server_socket);
        exit(EXIT_FAILURE);
    }

    // 监听连接
    // 使用 listen 函数开始监听客户端连接，等待被服务器接受的最大排队连接请求数为 50
    if (listen(server_socket, 50) == -1) {
        perror("监听失败");
        // 关闭已创建的套接字
        close(server_socket);
        exit(EXIT_FAILURE);
    }
    
    // 记录服务器启动信息，并在控制台打印服务器启动提示，告知用户服务器已准备好接受客户端连接。
    //system：服务器启动时调用 log_message 函数记录服务器启动日志。
    //NULL：表示没有额外的上下文信息。
    //NULL：表示没有相关的用户信息
    log_message("SYSTEM", NULL, "聊天室服务器启动", NULL); // 调用 log_message 函数记录服务器启动日志
    printf("聊天室服务器已启动，端口 %d，等待连接...\n", server_config.port);// 打印服务器启动信息，提示正在等待客户端连接

    // 持续接受客户端连接
    while (1) {
        // accept 函数：从监听队列中取出一个客户端连接请求，创建一个新的套接字用于和该客户端通信，返回新套接字的文件描述符。
        client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len);
        // 检查接受连接是否失败，若失败则打印错误信息并继续循环
        if (client_socket == -1) {
            perror("接受连接失败");
            continue;
        }

        // 检查聊天室人数
        // 加锁，确保在统计在线客户端数量时数据的一致性
        pthread_mutex_lock(&mutex); //保证在统计在线客户端数量时，不会有其他线程同时修改 clients 数组
        // 记录当前在线的客户端数量
        int client_count = 0;   //设置初始值
        // 遍历客户端数组，统计在线且已认证的客户端数量
        for (int i = 0; i < MAX_CLIENTS; i++) {
            // 检查当前客户端是否已连接且已认证
            if (clients[i].socket != -1 && clients[i].is_authenticated) {
                // 若满足条件，说明第 i 个客户端已连接且已认证，增加在线客户端数量
                client_count++;
            }
        }
        // 解锁，允许其他线程访问客户端数组
        pthread_mutex_unlock(&mutex);

        // 若聊天室已满，向客户端发送提示信息并关闭连接
        if (client_count >= MAX_CLIENTS) {
            char msg[] = "聊天室已满，无法加入\n";
            // 向客户端发送消息
            send(client_socket, msg, strlen(msg), 0);
            // 关闭客户端套接字
            close(client_socket);
            continue;
        }

        // 动态分配内存存储套接字描述符
        // 为新的客户端套接字描述符分配内存
        int *new_sock = malloc(sizeof(int));
        // 检查内存分配是否成功，若失败则打印错误信息并关闭连接
        if (!new_sock) {
            perror("内存分配失败");
            // 关闭客户端套接字
            close(client_socket);
            continue;
        }
        // 将客户端套接字描述符赋值给新分配的内存
        *new_sock = client_socket;
        
        // 使用 pthread_create 函数创建新线程处理新连接的客户端
        //&thread_id：用于存储新线程的标识符。
        //NULL：表示新线程使用默认属性。
        //handle_client：线程函数，用于处理客户端连接。
        //new_sock：传递给线程函数的参数，即客户端套接字描述符的指针。
        if (pthread_create(&thread_id, NULL, handle_client, new_sock) != 0) {
            perror("创建线程失败");
            // 关闭客户端套接字
            close(client_socket);
            // 释放动态分配的内存
            free(new_sock);
        }
        // 将新创建的线程设置为分离状态，线程结束后自动释放资源
        pthread_detach(thread_id);
    }

    // 清理资源
    // 关闭服务器套接字
    close(server_socket);
    // 销毁互斥锁
    pthread_mutex_destroy(&mutex);
    // 销毁用户数据库互斥锁
    destroy_user_db_mutex();
    
    // 记录服务器关闭
    // 调用 log_message 函数记录服务器关闭日志
    //system：服务器关闭时调用 log_message 函数记录服务器关闭日志。
    //NULL：表示没有额外的上下文信息，通常为 NULL。。
    //NULL：表示没有相关的用户信息，通常为 NULL。
    log_message("SYSTEM", NULL, "聊天室服务器关闭", NULL);
    
    return 0;
}